package gocomm

import (
	"compress/gzip"
	"container/list"
	"crypto/tls"
	"encoding/json"
	"encoding/pem"
	"encoding/xml"
	"errors"
	"io"
	"log"
	"net"
	"strconv"

	"bytes"
	"fmt"
	"golang.org/x/crypto/pkcs12"
	"golang.org/x/net/proxy"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

// 发送http请求 ，并返回请求后的数据
func DoHttp(method string, serviceUrl string, params map[string]string, headers map[string]string) []byte {
	client := &http.Client{Timeout: 50 * time.Second}

	var request *http.Request
	var response *http.Response

	if http.MethodPost == strings.ToUpper(method) {

		paramstr := ""
		if params != nil {
			i := 0
			for k, v := range params {
				if i != 0 {
					paramstr = paramstr + "&"
				}
				paramstr = paramstr + k + "=" + v
				i++
			}
		}
		var err error
		request, err = http.NewRequest(method, serviceUrl, strings.NewReader(paramstr))
		if err != nil {
			panic(err)
		}
		request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	} else {
		//request.Header.Set("Content-Type", "application/json; charset=utf-8")
		if params != nil {

			urlValue := url.Values{}
			for k, v := range params {
				urlValue.Add(k, v)
			}
			if strings.Index(serviceUrl, "?") == -1 {
				serviceUrl = serviceUrl + "?"
			}
			serviceUrl = serviceUrl + urlValue.Encode()
		}

		var err error
		request, err = http.NewRequest(method, serviceUrl, nil)
		if err != nil {
			panic(err)
		}
		//request.Header.Set("Content-Type", "application/json;charset:utf-8")

	}
	if headers != nil {
		for k, v := range headers {
			request.Header.Add(k, v)
		}
	}
	response, err := client.Do(request)
	if response == nil || err != nil {
		return nil
	}

	defer response.Body.Close()
	data, _ := ioutil.ReadAll(response.Body)
	return data
}

// data is jsonstring data
func SendPostJson(serviceUrl string, data string) (string, []byte) {
	var jsonStr = []byte(data)

	req, err := http.NewRequest(http.MethodPost, serviceUrl, bytes.NewBuffer(jsonStr))
	// req.Header.Set("X-Custom-Header", "myvalue")
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	fmt.Println("response Status:", resp.Status)
	fmt.Println("response Headers:", resp.Header)
	body, _ := ioutil.ReadAll(resp.Body)
	//fmt.Println("response Body:", string(body))

	return resp.Status, body

}

// 发送 POST 内容体数据
func SendPost(serviceUrl string, data string, contentType string) (string, []byte, error) {
	var databytes = []byte(data)

	req, err := http.NewRequest(http.MethodPost, serviceUrl, bytes.NewBuffer(databytes))

	req.Header.Set("Content-Type", contentType)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return resp.Status, nil, err
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	return resp.Status, body, nil

}

// HTTPGet get 请求
func HTTPGet(uri string) ([]byte, error) {

	client := &http.Client{
		Timeout: time.Second * time.Duration(10), // 设置超时时间为10秒
	}

	response, err := client.Get(uri)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		//return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
		return io.ReadAll(response.Body)
	}
	return io.ReadAll(response.Body)
}

// HTTPPost post 请求
func HTTPPost(uri string, data string) ([]byte, error) {
	body := bytes.NewBuffer([]byte(data))
	response, err := http.Post(uri, "", body)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// PostJSON post json 数据请求
// 直接传递结构体
func PostJSON(uri string, obj interface{}) ([]byte, error) {
	if obj == nil {
		obj = map[string]interface{}{}
	}
	jsonData, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
	body := bytes.NewBuffer(jsonData)
	response, err := http.Post(uri, "application/json;charset=utf-8", body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response)
	}
	return ioutil.ReadAll(response.Body)
}

// PostJSON post json 数据请求
// 直接传递结构体
func PostJSONForHeader(uri string, obj interface{}, headers map[string]string) ([]byte, http.Header, error) {
	if obj == nil {
		obj = map[string]interface{}{}
	}

	jsonData, err := json.Marshal(obj)
	if err != nil {
		return nil, http.Header{}, err
	}
	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
	body := bytes.NewBuffer(jsonData)

	req, err := http.NewRequest(http.MethodPost, uri, bytes.NewBuffer(body.Bytes()))

	req.Header.Set("Content-Type", "application/json")

	if headers != nil {
		for k, v := range headers {
			req.Header.Add(k, v)
		}
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, http.Header{}, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, resp)
	}
	defer resp.Body.Close()
	r, err := io.ReadAll(resp.Body)
	return r, resp.Header, err
}

// PostJSONWithRespContentType post json数据请求，且返回数据类型
func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, error) {
	jsonData, err := json.Marshal(obj)
	if err != nil {
		return nil, "", err
	}

	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)

	body := bytes.NewBuffer(jsonData)
	response, err := http.Post(uri, "application/json;charset=utf-8", body)
	if err != nil {
		return nil, "", err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, "", fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response)
	}
	responseData, err := ioutil.ReadAll(response.Body)
	contentType := response.Header.Get("Content-Type")
	return responseData, contentType, err
}

// PostFile 上传文件
func PostFile(fieldname, filename, uri string) ([]byte, error) {
	fields := []MultipartFormField{
		{
			IsFile:    true,
			Fieldname: fieldname,
			Filename:  filename,
		},
	}
	return PostMultipartForm(fields, uri)
}

// MultipartFormField 保存文件或其他字段信息
type MultipartFormField struct {
	IsFile    bool // 是否为文件 字段
	Fieldname string
	Value     []byte
	Filename  string
}

// PostMultipartForm 上传文件或其他多个字段
func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte, err error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	for _, field := range fields {
		if field.IsFile {
			fileWriter, e := bodyWriter.CreateFormFile(field.Fieldname, field.Filename)
			if e != nil {
				err = fmt.Errorf("error writing to buffer , err=%v", e)
				return
			}

			if field.Value != nil {
				// 从字节码 设置内容
				buffer := bytes.NewBuffer(field.Value)
				if _, err = io.Copy(fileWriter, buffer); err != nil {
					return
				}
			} else {
				fh, e := os.Open(field.Filename)
				if e != nil {
					err = fmt.Errorf("error opening file , err=%v", e)
					return
				}
				defer fh.Close()

				if _, err = io.Copy(fileWriter, fh); err != nil {
					return
				}
			}

		} else {
			partWriter, e := bodyWriter.CreateFormField(field.Fieldname)
			if e != nil {
				err = e
				return
			}
			valueReader := bytes.NewReader(field.Value)
			if _, err = io.Copy(partWriter, valueReader); err != nil {
				return
			}
		}
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	resp, e := http.Post(uri, contentType, bodyBuf)
	if e != nil {
		err = e
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, err
	}
	respBody, err = ioutil.ReadAll(resp.Body)
	return
}

// PostXML perform a HTTP/POST request with XML body
func PostXML(uri string, obj interface{}) ([]byte, error) {
	xmlData, err := xml.Marshal(obj)
	if err != nil {
		return nil, err
	}

	body := bytes.NewBuffer(xmlData)
	response, err := http.Post(uri, "application/xml;charset=utf-8", body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response)
	}
	return ioutil.ReadAll(response.Body)
}

// httpWithTLS CA证书
func httpWithTLS(rootCa, key string) (*http.Client, error) {
	var client *http.Client
	certData, err := ioutil.ReadFile(rootCa)
	if err != nil {
		return nil, fmt.Errorf("unable to find cert path=%s, error=%v", rootCa, err)
	}
	cert := pkcs12ToPem(certData, key)
	config := &tls.Config{
		Certificates: []tls.Certificate{cert},
	}
	tr := &http.Transport{
		TLSClientConfig:    config,
		DisableCompression: true,
	}
	client = &http.Client{Transport: tr}
	return client, nil
}

// pkcs12ToPem 将Pkcs12转成Pem
func pkcs12ToPem(p12 []byte, password string) tls.Certificate {
	blocks, err := pkcs12.ToPEM(p12, password)
	defer func() {
		if x := recover(); x != nil {
			log.Print(x)
		}
	}()
	if err != nil {
		panic(err)
	}
	var pemData []byte
	for _, b := range blocks {
		pemData = append(pemData, pem.EncodeToMemory(b)...)
	}
	cert, err := tls.X509KeyPair(pemData, pemData)
	if err != nil {
		panic(err)
	}
	return cert
}

// PostXMLWithTLS perform a HTTP/POST request with XML body and TLS
func PostXMLWithTLS(uri string, obj interface{}, ca, key string) ([]byte, error) {
	if obj == nil {
		obj = map[string]interface{}{}
	}
	xmlData, err := xml.Marshal(obj)
	if err != nil {
		return nil, err
	}

	body := bytes.NewBuffer(xmlData)
	client, err := httpWithTLS(ca, key)
	if err != nil {
		return nil, err
	}
	response, err := client.Post(uri, "application/xml;charset=utf-8", body)
	if err != nil {
		return nil, err
	}
	if response == nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// 直接传递结构体
// method: GET | POST
// url:请求地址
// timeout(秒) 10
func PostJSONEx(method string, uri string, obj interface{}, timeout int) ([]byte, error) {

	if obj == nil {
		obj = map[string]interface{}{}
	}
	jsonData, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
	body := bytes.NewBuffer(jsonData)

	//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(netw, addr string) (net.Conn, error) {
				conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
				if err != nil {
					return nil, err
				}
				conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
				return conn, nil
			},
			ResponseHeaderTimeout: time.Second * time.Duration(timeout),
		},
	}

	reqest, err := http.NewRequest(method, uri, body) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "application/json;charset=utf-8")
	if err != nil {
		return nil, err
	}
	response, err := client.Do(reqest)
	if response == nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// 直接传递结构体
// method: GET | POST
// url:请求地址
// timeout(秒) 10
func PostJSONHeaderEx(method string, uri string, obj interface{}, headers map[string]string, timeout int) ([]byte, error) {
	jsonData, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
	body := bytes.NewBuffer(jsonData)

	//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(netw, addr string) (net.Conn, error) {
				conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
				if err != nil {
					return nil, err
				}
				conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
				return conn, nil
			},
			ResponseHeaderTimeout: time.Second * time.Duration(timeout),
		},
	}

	reqest, err := http.NewRequest(method, uri, body) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "application/json;charset=utf-8")

	if headers != nil {
		for k, v := range headers {
			reqest.Header.Add(k, v)
		}
	}

	if err != nil {
		return nil, err
	}
	response, err := client.Do(reqest)
	if response == nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response)
	}
	return ioutil.ReadAll(response.Body)
}

// 直接传递结构体 - 支持cookie
// method: GET | POST
// url:请求地址
// timeout(秒) 10
func PostJSONHeaderCookieEx(method string, uri string, obj interface{}, headers map[string]string, timeout int, cookies []*http.Cookie, p *ProxyInfo) ([]byte, error) {
	jsonData, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1)
	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
	body := bytes.NewBuffer(jsonData)

	var client *http.Client

	//设定代理
	if p != nil && p.ProxyIp != "" {

		var ProxyUser *proxy.Auth

		if p.ProxyUser != "" && p.ProxyPassword != "" {
			ProxyUser = &proxy.Auth{
				User:     p.ProxyUser,
				Password: p.ProxyPassword,
			}
		} else {
			ProxyUser = nil
		}

		dialer, err := proxy.SOCKS5("tcp", p.ProxyIp, ProxyUser, proxy.Direct)

		if err != nil {
			return []byte{}, err
		}

		client = &http.Client{
			Transport: &http.Transport{
				Dial: dialer.Dial,
			},
			Timeout: time.Second * time.Duration(timeout),
		}
	} else {
		//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
		client = &http.Client{
			Transport: &http.Transport{
				Dial: func(netw, addr string) (net.Conn, error) {
					conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
					if err != nil {
						return nil, err
					}
					conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
					return conn, nil
				},
				ResponseHeaderTimeout: time.Second * time.Duration(timeout),
			},
		}
	}

	reqest, err := http.NewRequest(method, uri, body) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "application/json;charset=utf-8")

	if headers != nil {
		for k, v := range headers {
			reqest.Header[k] = []string{v} // 一定要这种写法
		}
	}

	// 添加cookie

	if cookies != nil {
		for _, v := range cookies {
			reqest.AddCookie(v)
		}
	}

	if err != nil {
		return nil, err
	}
	response, err := client.Do(reqest)
	if response == nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return ioutil.ReadAll(response.Body)
}

// ==== application/x-www-form-urlencoded
type FormItem struct {
	Name  string
	Value string
}

type ProxyInfo struct {
	ProxyIp       string
	ProxyUser     string
	ProxyPassword string
	Protocol      string // http | socks5
}

// 直接传递结构体 - 支持cookie
// method: GET | POST
// url:请求地址
// timeout(秒) 10
// application/x-www-form-urlencoded
// 需要拼接成 k1=v1&k2=v2, 保证顺序和map 的顺序一致的
// 使用list 接收参数 按照顺序
func PostFormUrlEncoded(method string, uri string, data list.List, headers map[string]string, timeout int, cookies []*http.Cookie, p *ProxyInfo) ([]byte, error) {

	var buf strings.Builder
	for elem := data.Front(); elem != nil; elem = elem.Next() {
		fi := elem.Value.(FormItem)
		if buf.Len() > 0 {
			buf.WriteByte('&')
		}
		buf.WriteString(fi.Name)
		buf.WriteByte('=')
		buf.WriteString(url.QueryEscape(fi.Value))
	}

	body := strings.NewReader(buf.String())

	var client *http.Client

	//设定代理
	if p != nil && p.ProxyIp != "" {

		var ProxyUser *proxy.Auth

		if p.ProxyUser != "" && p.ProxyPassword != "" {
			ProxyUser = &proxy.Auth{
				User:     p.ProxyUser,
				Password: p.ProxyPassword,
			}
		} else {
			ProxyUser = nil
		}

		dialer, err := proxy.SOCKS5("tcp", p.ProxyIp, ProxyUser, proxy.Direct)

		if err != nil {
			return []byte{}, err
		}

		client = &http.Client{
			Transport: &http.Transport{
				Dial: dialer.Dial,
			},
			Timeout: time.Second * time.Duration(timeout),
		}
	} else {
		//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
		client = &http.Client{
			Transport: &http.Transport{
				Dial: func(netw, addr string) (net.Conn, error) {
					conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
					if err != nil {
						return nil, err
					}
					conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
					return conn, nil
				},
				ResponseHeaderTimeout: time.Second * time.Duration(timeout),
			},
		}
	}

	reqest, err := http.NewRequest(method, uri, body) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	if headers != nil {
		for k, v := range headers {
			reqest.Header[k] = []string{v} // 一定要这种写法
		}
	}

	// 添加cookie

	if cookies != nil {
		for _, v := range cookies {
			reqest.AddCookie(v)
		}
	}

	if err != nil {
		return nil, err
	}
	response, err := client.Do(reqest)
	if response == nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	return io.ReadAll(response.Body)
}

// 直接传递结构体 - 支持cookie  支持参数顺序校验
// method: GET | POST
// url:请求地址
// timeout(秒) 10
// application/x-www-form-urlencoded
// 需要拼接成 k1=v1&k2=v2, 保证顺序和map 的顺序一致的
// 使用list 接收参数 按照顺序
func PostFormUrlEncodedEx(method string, uri string, data list.List, headers map[string]string, timeout int, cookies []*http.Cookie, p *ProxyInfo) ([]byte, http.Header, error) {

	var buf strings.Builder
	for elem := data.Front(); elem != nil; elem = elem.Next() {
		fi := elem.Value.(FormItem)
		if buf.Len() > 0 {
			buf.WriteByte('&')
		}
		buf.WriteString(fi.Name)
		buf.WriteByte('=')
		buf.WriteString(url.QueryEscape(fi.Value))
	}

	body := strings.NewReader(buf.String())

	var client *http.Client

	//设定代理
	if p != nil && p.ProxyIp != "" {

		var ProxyUser *proxy.Auth

		if p.ProxyUser != "" && p.ProxyPassword != "" {
			ProxyUser = &proxy.Auth{
				User:     p.ProxyUser,
				Password: p.ProxyPassword,
			}
		} else {
			ProxyUser = nil
		}

		dialer, err := proxy.SOCKS5("tcp", p.ProxyIp, ProxyUser, proxy.Direct)

		if err != nil {
			return []byte{}, nil, err
		}

		client = &http.Client{
			Transport: &http.Transport{
				Dial: dialer.Dial,
			},
			Timeout: time.Second * time.Duration(timeout),
		}
	} else {
		//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
		client = &http.Client{
			Transport: &http.Transport{
				Dial: func(netw, addr string) (net.Conn, error) {
					conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
					if err != nil {
						return nil, err
					}
					conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
					return conn, nil
				},
				ResponseHeaderTimeout: time.Second * time.Duration(timeout),
			},
		}
	}

	reqest, err := http.NewRequest(method, uri, body) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	if headers != nil {
		for k, v := range headers {
			reqest.Header[k] = []string{v} // 一定要这种写法
		}
	}

	// 添加cookie

	if cookies != nil {
		for _, v := range cookies {
			reqest.AddCookie(v)
		}
	}

	if err != nil {
		return nil, nil, err
	}
	response, err := client.Do(reqest)
	if response == nil {
		return nil, nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}

	d, _ := ioutil.ReadAll(response.Body)
	return d, response.Header, nil
}

// 直接传递结构体 - 支持cookie ,此接口不支持 带参数 顺序校验的情况
// method: GET | POST
// url:请求地址
// timeout(秒) 10
// application/x-www-form-urlencoded
// 需要拼接成 k1=v1&k2=v2, 保证顺序和map 的顺序一致的
// 使用list 接收参数 按照顺序
func PostFormUrlEncodedEx2(method string, uri string, data map[string]string, headers map[string]string, timeout int, cookies []*http.Cookie, p *ProxyInfo) ([]byte, http.Header, error) {

	var buf strings.Builder
	for k, v := range data {

		if buf.Len() > 0 {
			buf.WriteByte('&')
		}
		buf.WriteString(k)
		buf.WriteByte('=')
		buf.WriteString(url.QueryEscape(v))
	}

	body := strings.NewReader(buf.String())

	var client *http.Client

	//设定代理
	if p != nil && p.ProxyIp != "" {

		var ProxyUser *proxy.Auth

		if p.ProxyUser != "" && p.ProxyPassword != "" {
			ProxyUser = &proxy.Auth{
				User:     p.ProxyUser,
				Password: p.ProxyPassword,
			}
		} else {
			ProxyUser = nil
		}

		dialer, err := proxy.SOCKS5("tcp", p.ProxyIp, ProxyUser, proxy.Direct)

		if err != nil {
			return []byte{}, nil, err
		}

		client = &http.Client{
			Transport: &http.Transport{
				Dial: dialer.Dial,
			},
			Timeout: time.Second * time.Duration(timeout),
		}
	} else {
		//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
		client = &http.Client{
			Transport: &http.Transport{
				Dial: func(netw, addr string) (net.Conn, error) {
					conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
					if err != nil {
						return nil, err
					}
					conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
					return conn, nil
				},
				ResponseHeaderTimeout: time.Second * time.Duration(timeout),
			},
		}
	}

	reqest, err := http.NewRequest(method, uri, body) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	if headers != nil {
		for k, v := range headers {
			reqest.Header[k] = []string{v} // 一定要这种写法
		}
	}

	// 添加cookie

	if cookies != nil {
		for _, v := range cookies {
			reqest.AddCookie(v)
		}
	}

	if err != nil {
		return nil, nil, err
	}
	response, err := client.Do(reqest)
	if response == nil {
		return nil, nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}
	d, _ := ioutil.ReadAll(response.Body)
	return d, response.Header, nil
}

// 发送http请求 ，并返回请求后的数据
func DoHttpEx(method string, serviceUrl string, params map[string]string, headers map[string]string) ([]byte, error) {
	client := &http.Client{Timeout: 50 * time.Second}

	var request *http.Request
	var response *http.Response

	if http.MethodPost == strings.ToUpper(method) {

		paramstr := ""
		if params != nil {
			i := 0
			for k, v := range params {
				if i != 0 {
					paramstr = paramstr + "&"
				}
				paramstr = paramstr + k + "=" + v
				i++
			}
		}
		var err error
		request, err = http.NewRequest(method, serviceUrl, strings.NewReader(paramstr))
		if err != nil {
			panic(err)
		}
		request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	} else {
		//request.Header.Set("Content-Type", "application/json; charset=utf-8")
		if params != nil {

			urlValue := url.Values{}
			for k, v := range params {
				urlValue.Add(k, v)
			}
			if strings.Index(serviceUrl, "?") == -1 {
				serviceUrl = serviceUrl + "?"
			}
			serviceUrl = serviceUrl + urlValue.Encode()
		}

		var err error
		request, err = http.NewRequest(method, serviceUrl, nil)
		if err != nil {
			return nil, err
		}
		//request.Header.Set("Content-Type", "application/json;charset:utf-8")

	}
	if headers != nil {
		for k, v := range headers {
			request.Header.Add(k, v)
		}
	}
	response, err := client.Do(request)
	if response == nil || err != nil {
		return nil, err
	}

	defer response.Body.Close()
	data, err := ioutil.ReadAll(response.Body)
	return data, err
}

// 直接传递结构体 - 支持cookie  支持参数顺序校验
// method: GET | POST
// url:请求地址
// timeout(秒) 10
// application/x-www-form-urlencoded
// 需要拼接成 k1=v1&k2=v2, 保证顺序和map 的顺序一致的
// 使用list 接收参数 按照顺序
func PostData(method string, uri string, data string, headers map[string]string, timeout int, cookies []*http.Cookie, p *ProxyInfo) ([]byte, http.Header, error) {

	body := strings.NewReader(data)

	var client *http.Client

	//设定代理s
	if p != nil && p.ProxyIp != "" {

		if p.Protocol == "socks5" {
			var ProxyUser *proxy.Auth

			if p.ProxyUser != "" {
				ProxyUser = &proxy.Auth{
					User:     p.ProxyUser,
					Password: p.ProxyPassword,
				}
			} else {
				ProxyUser = nil
			}

			dialer, err := proxy.SOCKS5("tcp", p.ProxyIp, ProxyUser, proxy.Direct)

			if err != nil {
				return []byte{}, nil, err
			}

			client = &http.Client{
				Transport: &http.Transport{
					Dial: dialer.Dial,
				},
				Timeout: time.Second * time.Duration(timeout),
			}
		} else { // http

			ip_port := strings.Split(p.ProxyIp, ":")
			ip := ip_port[0]
			port, _ := strconv.Atoi(ip_port[1])
			var proxyUrl string
			if p.ProxyUser == "" {

				proxyUrl = fmt.Sprintf("http://%s:%d", ip, port)
			} else {
				proxyUrl = fmt.Sprintf("http://%s:%s@%s:%d", p.ProxyUser, p.ProxyPassword, ip, port)
			}

			proxy, err := url.Parse(proxyUrl)
			if err != nil {
				return []byte{}, nil, err
			}
			netTransport := &http.Transport{
				Proxy: http.ProxyURL(proxy),
			}
			client = &http.Client{
				Timeout:   time.Second * time.Duration(timeout), //设置连接超时时间
				Transport: netTransport,
			}

		}

	} else {
		//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
		client = &http.Client{
			Transport: &http.Transport{
				Dial: func(netw, addr string) (net.Conn, error) {
					conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
					if err != nil {
						return nil, err
					}
					conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
					return conn, nil
				},
				ResponseHeaderTimeout: time.Second * time.Duration(timeout),
			},
		}
	}

	reqest, err := http.NewRequest(method, uri, body) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")

	if headers != nil {
		for k, v := range headers {
			reqest.Header[k] = []string{v} // 一定要这种写法
		}
	}

	// 添加cookie

	if cookies != nil {
		for _, v := range cookies {
			reqest.AddCookie(v)
		}
	}

	if err != nil {
		return nil, nil, err
	}
	response, err := client.Do(reqest)
	if err != nil {
		return nil, nil, fmt.Errorf("http get error : uri=%v , err=%v", uri, err)
	}
	if response == nil {
		return nil, nil, errors.New("response is null")
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return nil, nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}

	var r []byte

	if response.Header.Get("Content-Encoding") == "gzip" {

		gzipReader, err := gzip.NewReader(response.Body)
		if err != nil {
			fmt.Println("Error creating gzip reader:", err)
			return r, response.Header, err
		}
		defer gzipReader.Close()

		// 使用gzip reader读取和解压的响应体
		r, err = io.ReadAll(gzipReader)
		if err != nil {
			fmt.Println("Error reading response body:", err)
			return r, response.Header, err
		}

	} else {

		r, err = io.ReadAll(response.Body)
	}
	return r, response.Header, nil
}

// 直接传递结构体 - 支持cookie  支持参数顺序校验
// method: GET | POST
// url:请求地址
// timeout(秒) 10
// application/x-www-form-urlencoded
// 需要拼接成 k1=v1&k2=v2, 保证顺序和map 的顺序一致的
// 使用list 接收参数 按照顺序
func HttpGetGo(uri string, headers map[string]string, timeout int, cookies []*http.Cookie, p *ProxyInfo) ([]byte, *http.Response, error) {

	var client *http.Client

	//设定代理s
	if p != nil && p.ProxyIp != "" {

		var ProxyUser *proxy.Auth

		if p.ProxyUser != "" && p.ProxyPassword != "" {
			ProxyUser = &proxy.Auth{
				User:     p.ProxyUser,
				Password: p.ProxyPassword,
			}
		} else {
			ProxyUser = nil
		}

		dialer, err := proxy.SOCKS5("tcp", p.ProxyIp, ProxyUser, proxy.Direct)

		if err != nil {
			return []byte{}, nil, err
		}

		client = &http.Client{
			Transport: &http.Transport{
				Dial: dialer.Dial,
			},
			Timeout: time.Second * time.Duration(timeout),
		}
	} else {
		//可以通过client中transport的Dail函数,在自定义Dail函数里面设置建立连接超时时长和发送接受数据超时
		client = &http.Client{
			Transport: &http.Transport{
				Dial: func(netw, addr string) (net.Conn, error) {
					conn, err := net.DialTimeout(netw, addr, time.Second*3) //设置建立连接超时
					if err != nil {
						return nil, err
					}
					conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeout))) //设置发送接受数据超时
					return conn, nil
				},
				ResponseHeaderTimeout: time.Second * time.Duration(timeout),
			},
		}
	}

	reqest, err := http.NewRequest("GET", uri, nil) //提交请求;用指定的方法，网址，可选的主体放回一个新的*Request
	reqest.Header.Set("Content-Type", "text/html")
	reqest.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36")

	if headers != nil {
		for k, v := range headers {
			reqest.Header[k] = []string{v} // 一定要这种写法
		}
	}

	// 添加cookie

	if cookies != nil {
		for _, v := range cookies {
			reqest.AddCookie(v)
		}
	}

	if err != nil {
		return nil, nil, err
	}
	response, err := client.Do(reqest)
	if response == nil {
		return nil, nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
		return nil, nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
	}

	d, _ := io.ReadAll(response.Body)
	return d, response, nil
}
